package controllers

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"lottery/comm"
	"lottery/conf"
	"lottery/models"
	"lottery/utils"
	"net/http"
)

//  函数中大量进行异常处理， 其实可以将异常提取出来， 进行统一处理，看起来更加的好看（基础篇中有讲到）
func (idx *IndexController) GetLucky(c *gin.Context) {
	rs := gin.H{}
	rs["code"] = 0
	rs["msg"] = ""
	// 1. 验证登录用户
	loginUser := comm.GetLoginUser(c.Request)
	if loginUser == nil || loginUser.Uid < 1 {
		rs["code"] = 101 // 错误码
		rs["msg"] = "请先登录, 再来抽奖"
		c.JSON(http.StatusOK, rs)
		return
	}
	// 2. 用户抽奖分布式锁定： 防止用户多次尝试或者刷接口
	ok := utils.LockLucky(loginUser.Uid)
	if ok {
		// 解锁操作
		defer utils.UnlockLucky(loginUser.Uid)
	} else {
		rs["code"] = 102 // 错误码
		rs["msg"] = "正在抽奖, 请稍后重试"
		c.JSON(http.StatusOK, rs)
		return
	}
	// 3. 验证用户今日参与次数
	// redis 递增操作
	userDayNum := utils.IncrUserLuckyNum(loginUser.Uid)
	// 判断是否大于 今日用户中间记录数
	if userDayNum > conf.UserPrizeMax {
		rs["code"] = 103
		rs["msg"] = "今日的抽奖次数已用完, 明天再来吧！"
		c.JSON(http.StatusOK, rs)
	} else { // redis 中不大于，这时还要比对数据库，数据同步问题
		// 传入缓存的数量
		ok = idx.checkUserday(c, loginUser.Uid, userDayNum)
		if !ok {
			rs["code"] = 103
			rs["msg"] = "今日的抽奖次数已用完, 明天再来吧！"
			c.JSON(http.StatusOK, rs)
			return
		}
	}
	// 4. 验证 ip 今日的参与次数
	ip := comm.ClientIp(c.Request)
	// 对 ip参与次数进行递增处理（原子操作），方法封装到utils里。通过redis去操作，并发量较大！数据库中没有ip计数的相应字段，纯redis
	// 注意：这里需要使用原子性操作了！！ 因为前面的分布式锁，并不能阻止 多个用户使用同一ip, ip自增的操作是并发的
	ipDayNum := utils.IncrIpLuckNum(ip)
	if ipDayNum > conf.IpLimitMax {
		rs["code"] = 104
		rs["msg"] = "同一Ip参与次数太多，明天再来参与吧"
		c.JSON(http.StatusOK, rs)
		return
	}

	// 黑名单： limitBlack == True，说明在黑名单里。5. 6步也是判断是否是黑名单
	limitBlack := false
	// 这个值IpPrizeMax 远小于 IpLimitMax，当ip超过这个值，就限制他，可以抽奖但是抽不中
	// 第八步中抽奖时会判断这个值
	if ipDayNum > conf.IpPrizeMax {
		limitBlack = true
	}
	// 5. 验证 ip 黑名单
	var blackipInfo *models.LtBlackIp
	if !limitBlack {
		ok, blackipInfo = idx.checkBlackip(ip)
		if !ok {
			fmt.Println("黑名单中的IP", ip, limitBlack)
			limitBlack = true
		}
	}
	// 6. 验证 用户 黑名单
	var userInfo *models.LtUser
	if !limitBlack {
		// TODO： 这一点我觉得逻辑大有问题！ 传进去的是loginUser.Uid 但是查找的确是 LtUser表的 id字段？ 没有关联
		ok, userInfo = idx.checkBlackUser(loginUser.Uid)
		if !ok {
			fmt.Println("黑名单中的用户", loginUser.Uid, limitBlack)
			limitBlack = true
		}
	}
	// 7. 抽奖编码
	prizeCode := comm.Random(10000) // 中奖概率默认是10000， 这里从10000 随机获取一个数字，当做中奖区间
	// 8. 匹配奖品是否中奖
	// prizeGift ，记录的是虚拟奖，虚拟奖直接发放。
	prizeGift := idx.prize(prizeCode, limitBlack)
	// 如果 没有中奖，或者 中奖奖品数量小于 0, 或者剩余数量小于 0
	if prizeGift == nil || prizeGift.PrizeNum < 0 ||
		(prizeGift.PrizeNum > 0 && prizeGift.LeftNum <= 0) {
		rs["code"] = 105
		rs["msg"] = "很遗憾，没有中奖，请下次再试（未中奖或者出现问题）"
		c.JSON(http.StatusOK, rs)
		return
	}
	fmt.Println("111111", prizeGift)
	// 我觉得这里应该加上 事务处理， 保存中奖结果 以及 这里的对库存进行更新，是一个整体进行原子性的。
	// 9. 有限制奖品发放
	if prizeGift.PrizeNum > 0 { // >0 说明奖品数量有限制, 进行一些处理
		if utils.GetGiftPoolNum(prizeGift.Id) <= 0 {
			// 数量小于0， 不能进行抽奖
			rs["code"] = 106
			rs["msg"] = "很遗憾，没有中奖，请下次再试（无奖可发）"
			c.JSON(http.StatusOK, rs)
			return
		}
		// 注意： 这一步处理要求是 原子性的！！ 多个用户可能同时走到这里，对数据进行递减操作。
		ok = utils.PrizeGift(prizeGift.Id, prizeGift.LeftNum)
		if !ok {
			rs["code"] = 107
			rs["msg"] = "很遗憾，没有中奖，请下次再试（无奖可发）"
			c.JSON(http.StatusOK, rs)
			return
		}
	}
	// 10. 不同编码的优惠券的发放
	if prizeGift.Gtype == conf.GtypeCodeDiff {
		code := utils.PrizeCodeDiff(prizeGift.Id, idx.ServiceCode)
		if code == "" {
			rs["code"] = 108
			rs["msg"] = "很遗憾， 没有中奖， 请下次再试（券）"
			c.JSON(http.StatusOK, rs)
			return
		}
		prizeGift.Gdata = code
	}
	// 11. 记录中奖记录
	result := models.LtResult{
		GiftId:     prizeGift.Id,
		GiftName:   prizeGift.Title,
		GiftType:   prizeGift.Gtype,
		Uid:        loginUser.Uid,
		Username:   loginUser.Username,
		PrizeCode:  prizeCode,
		GiftData:   prizeGift.Gdata,
		SysCreated: comm.NowUnix(),
		SysIp:      ip,
		SysStatus:  0,
	}
	// 保存
	err := idx.ServiceResult.Create(&result)
	if err != nil {
		log.Println("index_lucky.GetLucky ServiceResult.Create ",
			result, ", error = ", err)
		rs["code"] = 109
		rs["msg"] = "很遗憾没有中奖！（中奖记录没有保持成功）"
		c.JSON(http.StatusOK, rs)
		return
	}
	if prizeGift.Gtype == conf.GtypeGiftLarge {
		// 获得实物大奖， 需要将用户，ip设置为黑名单一段时间， 运气太好了，不能再得了
		idx.prizeLarge(ip, loginUser, userInfo, blackipInfo)
	}
	// 12 返回抽奖结果
	rs["gift"] = prizeGift
	rs["msg"] = "恭喜中奖！"
	c.JSON(http.StatusOK, rs)
}
